package de.umr.fb12.vs.os.uebung;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import de.umr.fb12.vs.os.CoreScheduler;
import de.umr.fb12.vs.os.SchedClass;
import de.umr.fb12.vs.os.Task;

public class LotterySched implements SchedClass {
	//additional data structures for this scheduling type
	
	// lottery time quantum
	private final static int quantum = 300;
	// auxiliary variable for quantum
	private long start_time = 0;
	
	/**
	 * the Ticket class represents a ticket which belongs to a certain task and is put into the lottery
	 */
	private class Ticket {
		
		/**
		 * Currency which the ticket belongs to
		 */
		private Currency currency;
		
		public Currency getCurrency() {
			return currency;
		}

		public Ticket(Currency currency) {
			this.currency = currency;
		}
	}
	
	/**
	 * the Currency class represents a Task and its assigned tickets
	 */
	private class Currency {
		
		/**
		 * set of tickets
		 */
		private Set<Ticket> assignedTickets = new HashSet<Ticket>();
		
		/**
		 * task
		 */
		private Task task;

		public Set<Ticket> getAssignedTickets() {
			return assignedTickets;
		}

		public Task getTask() {
			return task;
		}
		
		public Currency(Task task) {
			this.task = task;
			
			this.createTickets(1);
		}
		
		@SuppressWarnings("unused")
		public Currency(Task task, int amountOfTickets) {
			this.task = task;
			
			this.createTickets(amountOfTickets);
		}
		
		/**
		 * creates a certain number of tickets belonging to the given task
		 * @param num number of tickets
		 */
		private void createTickets(int num) {
			
			if(num <= 0)
				this.assignedTickets.add(new Ticket(this));
			else
				for(int i=0;i<num;i++)
					this.assignedTickets.add(new Ticket(this));
		}
	}
	
	/**
	 * Map to support finding the right Currency by a given Task
	 */
	private Map<Task, Currency> taskCurrencyMap = new HashMap<Task, Currency>();
	
	/**
	 * set of Currencies which belong to the tasks waiting for the CPU
	 */
	private Set<Currency> waitingTasks = new HashSet<Currency>();
	
	/**
	 * list of all tickets in the lottery
	 */
	private List<Ticket> ticketLottery = new ArrayList<Ticket>(); 

	@Override
	public void enqueue_task(Task curr, Task enq) {
		// if a task is enqueued while the idle thread is active:
		// set_tsk_need_resched, so that schedule is executed
		if (curr.isIdle_task())
			curr.set_tsk_need_resched();
		//create a new currency and adding it to the waiting tasks and its tickets to the lottery
		Currency c = new Currency(enq);
		this.waitingTasks.add(c);
		this.ticketLottery.addAll(c.getAssignedTickets());
		this.taskCurrencyMap.put(enq, c);
	}

	@Override
	public void dequeue_task(Task curr, Task deq) {
		
		// if the task which shall be dequeued is the current:
		// set_tsk_need_resched, so that schedule() is executed
		if (curr == deq)
			curr.set_tsk_need_resched();
		// remove deq from the waiting set and delete its tickets and deletes its mapping
		Currency c = taskCurrencyMap.get(deq);
		this.waitingTasks.remove(c);
		if(c!=null)
			this.ticketLottery.removeAll(c.getAssignedTickets());
		this.taskCurrencyMap.remove(deq);
	}

	@Override
	public void yield_task(Task curr, Task t) {
		// if the current task yields, then call set_tsk_need_resched to
		// call schedule()
		if (curr == t)
			t.set_tsk_need_resched();
	}

	@Override
	public Task pick_next_task() {
		Task next = null;
		// if we are asked for next task and we have one, return it and set the
		// start_time of the task
		if (this.waitingTasks.size() > 0) {
			start_time = System.currentTimeMillis();
			//pick a random task by picking a ticket randomly
			next = this.ticketLottery.get((int)(Math.random()*this.ticketLottery.size())).getCurrency().getTask();;
		}
		return next;		
	}

	@Override
	public void put_prev_task(Task prev) {
		//do nothing because the tasks ticket(s) is/are already in the lottery
	}

	@Override
	public void task_tick(Task curr) {
		// if we have a other task than the running and its time slice is up,
		// set it to reschedule
		if (this.waitingTasks.size() > 0
				&& System.currentTimeMillis() > start_time + quantum
				&& curr.getSchedclass() == this)
			curr.set_tsk_need_resched();
	}

	@Override
	public void task_new(Task curr, Task newtsk) {
		//create a new currency and adding it to the waiting tasks and its tickets to the lottery
		Currency c = new Currency(newtsk);
		this.waitingTasks.add(c);
		this.ticketLottery.addAll(c.getAssignedTickets());
		this.taskCurrencyMap.put(newtsk, c);
		//System.out.println(newtsk.getPid()+"|"+taskCurrencyMap.size()+" "+this.ticketLottery.size()+" "+this.waitingTasks.size());
	}

	public static void main(String[] args) {
		LotterySched lotSched = new LotterySched();
		CoreScheduler.setVerbose(true);
		CoreScheduler.init(lotSched, "asm/simple_init.asm",
				"asm/simple_dummy.asm");
	}
}
